
# 导入操作系统相关功能的模块
import os
# 导入用于高级文件操作的模块
import shutil
# 从string模块导入大写字母常量
from string import ascii_uppercase
# 从random模块导入随机选择函数
from random import choice

# 导入src框架中的Wizards模块
from src import Wizards


def onerror(func, path, exc_info):
    """
    Error handler for ``shutil.rmtree``.

    If the error is due to an access error (read only file)
    it attempts to add write permission and then retries.

    If the error is for another reason it re-raises the error.

    Usage : ``shutil.rmtree(path, onerror=onerror)``

    :param func: 引发错误的函数
    :param path: 引发错误的文件或目录路径
    :param exc_info: 异常信息元组
    """
    import stat
    # Is the error an access error?
    if not os.access(path, os.W_OK):
        # 若文件或目录没有写入权限，则添加写入权限并重新调用函数
        os.chmod(path, stat.S_IWUSR)
        func(path)
    else:
        pass


def generatePackage(
        packageName,
        newPackageRoot,
        bIncludeClassNode=True,
        bIncludeFooLib=True,
        bIncludeUINodeFactory=True,
        bIncludePin=True,
        bIncludeUIPinFactory=True,
        bIncludeTool=True,
        bIncludeExporter=True,
        bIncludePinInputWidgetFactory=True,
        bIncludePrefsWindget=False,
):
    """
    生成一个新的包，基于指定的模板目录，并根据传入的参数选择包含的组件。

    :param packageName: 新包的名称
    :param newPackageRoot: 新包的根目录路径
    :param bIncludeClassNode: 是否包含类节点组件，默认为True
    :param bIncludeFooLib: 是否包含函数库组件，默认为True
    :param bIncludeUINodeFactory: 是否包含UI节点工厂组件，默认为True
    :param bIncludePin: 是否包含引脚组件，默认为True
    :param bIncludeUIPinFactory: 是否包含UI引脚工厂组件，默认为True
    :param bIncludeTool: 是否包含工具组件，默认为True
    :param bIncludeExporter: 是否包含导出器组件，默认为True
    :param bIncludePinInputWidgetFactory: 是否包含引脚输入窗口工厂组件，默认为True
    :param bIncludePrefsWindget: 是否包含首选项窗口组件，默认为False
    """
    # 获取Wizards模块的路径
    wizardsRoot = Wizards.__path__[0]
    # 拼接模板目录的路径
    templatesRoot = os.path.join(wizardsRoot, "Templates")
    # 拼接包模板目录的路径
    packageTemplateDirPath = os.path.join(templatesRoot, "PackageTemplate")
    # 拼接新包的完整路径
    newPackagePath = os.path.join(newPackageRoot, packageName)

    if os.path.exists(newPackagePath):
        # 若新包路径已存在，则删除该路径下的所有内容
        shutil.rmtree(newPackagePath)
    # 复制包模板目录到新包路径
    shutil.copytree(packageTemplateDirPath, newPackagePath)

    # 遍历新包路径下的所有文件和目录
    for path, dirs, files in os.walk(newPackagePath):
        for newFileName in files:
            # 将文件名中的.txt替换为.py
            pyFileName = newFileName.replace(".txt", ".py")
            # 拼接.py文件的完整路径
            pyFilePath = os.path.join(path, pyFileName)
            # 拼接.txt文件的完整路径
            txtFilePath = os.path.join(path, newFileName)
            with open(txtFilePath, "r") as f:
                # 读取.txt文件的内容
                txtContent = f.read()
                # 将文件内容中的@PACKAGE_NAME替换为新包的名称
                pyContent = txtContent.replace("@PACKAGE_NAME", packageName)
                # 生成一个5位的随机大写字母字符串，并替换文件内容中的@RAND
                pyContent = pyContent.replace(
                    "@RAND", "".join([choice(ascii_uppercase) for i in range(5)])
                )
                with open(pyFilePath, "w") as pf:
                    # 将处理后的内容写入.py文件
                    pf.write(pyContent)
            # 删除.txt文件
            os.remove(txtFilePath)

    # remove unneeded directories
    # 再次遍历新包路径下的所有文件和目录，移除不需要的目录和文件
    for path, dirs, files in os.walk(newPackagePath):
        # 获取当前目录的名称
        dirName = os.path.basename(path)
        if dirName == "Nodes" and not bIncludeClassNode:
            # 若不包含类节点组件，则删除Nodes目录
            shutil.rmtree(path, ignore_errors=False, onexc=onerror)
        if dirName == "FunctionLibraries" and not bIncludeFooLib:
            # 若不包含函数库组件，则删除FunctionLibraries目录
            shutil.rmtree(path, ignore_errors=False, onexc=onerror)
        if dirName == "Pins" and not bIncludePin:
            # 若不包含引脚组件，则删除Pins目录
            shutil.rmtree(path, ignore_errors=False, onexc=onerror)
        if dirName == "Tools" and not bIncludeTool:
            # 若不包含工具组件，则删除Tools目录
            shutil.rmtree(path, ignore_errors=False, onexc=onerror)
        if dirName == "Exporters" and not bIncludeExporter:
            # 若不包含导出器组件，则删除Exporters目录
            shutil.rmtree(path, ignore_errors=False, onexc=onerror)
        if dirName == "PrefsWidgets" and not bIncludePrefsWindget:
            # 若不包含首选项窗口组件，则删除PrefsWidgets目录
            shutil.rmtree(path, ignore_errors=False, onexc=onerror)
        if dirName == "Factories":
            # 初始化已移除的工厂组件数量
            removedFactoriesCount = 0

            if not bIncludeUINodeFactory:
                # 若不包含UI节点工厂组件，则删除UINodeFactory.py文件
                os.remove(os.path.join(path, "UINodeFactory.py"))
                removedFactoriesCount += 1
            if not bIncludeUIPinFactory:
                # 若不包含UI引脚工厂组件，则删除UIPinFactory.py文件
                os.remove(os.path.join(path, "UIPinFactory.py"))
                removedFactoriesCount += 1
            if not bIncludePinInputWidgetFactory:
                # 若不包含引脚输入窗口工厂组件，则删除PinInputWidgetFactory.py文件
                os.remove(os.path.join(path, "PinInputWidgetFactory.py"))
                removedFactoriesCount += 1

            if removedFactoriesCount == 3:
                # 若所有工厂组件都被移除，则删除Factories目录
                shutil.rmtree(path, ignore_errors=False, onexc=onerror)

        if dirName == "UI":
            # 初始化已移除的UI类数量
            removedUIClasses = 0

            if not bIncludePin or not bIncludeUIPinFactory:
                # 若不包含引脚组件或UI引脚工厂组件，则删除UIDemoPin.py文件
                os.remove(os.path.join(path, "UIDemoPin.py"))
                removedUIClasses += 1

            if not bIncludeClassNode or not bIncludeUINodeFactory:
                # 若不包含类节点组件或UI节点工厂组件，则删除UIDemoNode.py文件
                os.remove(os.path.join(path, "UIDemoNode.py"))
                removedUIClasses += 1

            if removedUIClasses == 2:
                # 若所有UI类都被移除，则删除UI目录
                shutil.rmtree(path, ignore_errors=False, onexc=onerror)